package container

import (
	"io"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"

	"gitee.com/chuanjinge/go-container/container/cgroup"
	"gitee.com/chuanjinge/go-container/pkg/loggers"
)

// 真正在执行的程序

const fdPipe uintptr = 3

// 读取通过管道发送过来的字符串
func ReadUserCommand() []string {
	// 除去 stdin/stdout/stderr ，管道描述符就是第4个文件描述，下标为3，所以我们读取当前进程中的 fd=3 的文件描述符即可。
	// NewFile方法可以从文件描述符的值生成 *os.File 类型对象，就好像 Open方法可以从文件名获取到 *os.File 一样。
	readPipe := os.NewFile(fdPipe, "PipeInheritParent")
	cmdByte, err := io.ReadAll(readPipe)
	if err != nil {
		log.Printf("管道读取端读取错误:%v\n", err)
		return nil
	}
	cmd := string(cmdByte)
	return strings.Split(cmd, " ")
}

// 给新进程附加一个匿名管道
func CreatePipeToCmd(cmd *exec.Cmd) (*os.File, error) {
	// 创建一个 pipe 管道，稍后 exec.Command 创建子进程的时候带入到子进程之中
	readPipe, writePipe, err := os.Pipe()
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	cmd.ExtraFiles = []*os.File{readPipe} // 将读端带入到子进程中，实现父写子读的效果
	return writePipe, nil
}

func PushMsgToPipeAndClose(writePipe *os.File, msg []string) {
	writePipe.WriteString(strings.Join(msg, " "))
	writePipe.Close()
}

// syscall.Exec执行的shell命令会替换掉当前进程的进程空间
// 如果当前进程是在新PID命名空间中创建的，那么pid=1
// @param string containerId 预分配的容器ID
// @param []string volumeList 共享的目录名称
func RunReplaceProcess(containerId string, volumeList []string) error {
	// 设置hostname为一个固定值，随后改为依赖命令行参数
	syscall.Sethostname([]byte(containerId))
	// 设置联合文件系统及各种挂载，包括新的文件系统挂载在新的根目录、包括proc系统和tmpfs的挂载
	if err := PrepareUfs(containerId, volumeList); err != nil {
		loggers.GetLoggers().LoggerRun.Fatal(err.Error())
		return err
	}
	// 读取父进程传过来的非标识符参数
	command := ReadUserCommand() // command 比如 /bin/bash 或者 /bin/sh 或者 /bin/bash -c ls
	// 执行命令之前，根据环境变量 PATH 中列出的目录来搜索可执行文件，并返回该文件的绝对路径。
	// 从而避免在运行时遇到 "command not found" 错误。
	path, err := exec.LookPath(command[0])
	if err != nil {
		loggers.GetLoggers().LoggerRun.Fatalf("无法找到可执行的文件：%v", err)
		return err
	}
	// Exec方法和系统调用 execv 的调用很像；会将执行的命令替换掉当前进程；第三个参数指定环境变量，此处继承父进程的
	// log.Printf("realproc:exec：%s; %v; %v", path, path, os.Environ())
	command[0] = path
	if err := syscall.Exec(path, command, os.Environ()); err != nil {
		loggers.GetLoggers().LoggerRun.Fatal("Syscall.Exec Error: ", err.Error())
	}
	return nil
}

// 限制内存使用
// @param limit int64 限制最大的内存组的内存使用量。
func LimitMemory(limit int) error {
	// 获取 /proc 子系统路径
	path, err := cgroup.JoinHierarchy("memory", "xxoo")
	if err != nil {
		return err
	}
	err = cgroup.SetLimit(path, "memory.limit_in_bytes", strconv.Itoa(limit))
	if err != nil {
		return err
	}
	err = cgroup.AppendPid(path, os.Getpid())
	if err != nil {
		return err
	}
	return nil
}

// 限制CPU的时间片限制
func LimitCpu() {}

// 限制CPU使用第几个核
func LimitCpuSet() {}
